#include "SphereScene.h"

#include <cassert>
#include <d3d11.h>
#include <DirectXColors.h>
#include <DirectXMath.h>
#include <vector>

#include "main/D3DData.h"
#include "main/Globals.h"
#include "main/WindowManager.h"

#include "managers/PipelineStatesManager.h"

#include "utils/D3DUtils.h"
#include "utils/DxErrorChecker.h"
#include "utils/GeometryBuffers.h"
#include "utils/MathUtils.h"

using namespace DirectX;

SphereScene::SphereScene()
	: mSettings(L"configs/scenes/sphereScene.json")
	, mConstantBuffers(mSettings)
	, mResources(mSettings)
{

}

void
SphereScene::init() {
	initCamera();

	initPipeline();
}

void
SphereScene::draw() {
	assert(Globals::gD3D->mImmediateContext);
	ID3D11DeviceContext& context = *Globals::gD3D->mImmediateContext;

	//
	// Pre frame settings
	//
	{
		assert(Globals::gD3D->mRenderTargetView);
		ID3D11RenderTargetView& renderTargetView = *Globals::gD3D->mRenderTargetView;

		const float* clearColor = reinterpret_cast<const float*>(&Colors::Silver);

		assert(Globals::gD3D->mDepthStencilView);
		ID3D11DepthStencilView& depthStencilView = *Globals::gD3D->mDepthStencilView;

		context.ClearRenderTargetView(&renderTargetView, clearColor);
		const UINT flags = D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL;
		context.ClearDepthStencilView(&depthStencilView, flags, 1.0f, 0);
		
		Globals::gCamera.updateViewMatrix();
	}


	//
	// Compute Shader
	//
	if (Globals::gExecCS) {
		// Shader
		context.CSSetShader(mShaders.mCS, nullptr, 0);

		// Resources
		{
			const uint32_t numResources = 2;
			ID3D11UnorderedAccessView* resources[numResources] = { nullptr };
			resources[0] = mResources.mEntitiesPosWUAV;
			resources[1] = mResources.mEntitiesVelWUAV;
			context.CSSetUnorderedAccessViews(0, numResources, resources, nullptr);
		}

		// Dispatch
		context.Dispatch(mSettings.mDispatchGroupsX, mSettings.mDispatchGroupsY, mSettings.mDispatchGroupsZ);

		// Clear compute shader configs
		context.CSSetShader(nullptr, nullptr, 0);

		// Resources
		{
			const uint32_t numResources = 2;
			ID3D11UnorderedAccessView* resources[numResources] = { nullptr };
			context.CSSetUnorderedAccessViews(0, numResources, resources, nullptr);
		}
	}


	//
	// Vertex shader Stage
	//

	// Resources
	{
		const uint32_t numResources = 1;
		ID3D11ShaderResourceView* resources[numResources] = { nullptr };
		resources[0] = mResources.mEntitiesPosWSRV;
		context.VSSetShaderResources(0, numResources, resources);
	}


	//
	// Geometry Shader Stage
	//

	// Per Frame Constant Buffer
	{
		const XMMATRIX view = Globals::gCamera.viewMatrix();
		XMStoreFloat4x4A(&mConstantBuffers.mGSPerFrameBuffer.mData.mView, XMMatrixTranspose(view));

		const XMMATRIX proj = Globals::gCamera.projMatrix();
		XMStoreFloat4x4A(&mConstantBuffers.mGSPerFrameBuffer.mData.mProj, XMMatrixTranspose(proj));

		copyData(&mConstantBuffers.mGSPerFrameBuffer.mData, sizeof(mConstantBuffers.mGSPerFrameBuffer.mData), *mConstantBuffers.mGSPerFrameBuffer.mBuffer);
	}


	//
	// Draw
	// 
	{
		const uint32_t numEntities = mSettings.mNumEntities;
		context.Draw(numEntities, 0);
	}


	//
	// Post frame settings
	//

	// Resources
	{
		const uint32_t numResources = 1;
		ID3D11ShaderResourceView* resources[numResources] = { nullptr };
		context.VSSetShaderResources(0, numResources, resources);
	}

	// Present results
	{
		const HRESULT result = Globals::gD3D->mSwapChain->Present(0, 0);
		DxErrorChecker(result);
	}
}

void
SphereScene::destroy() {
}

void 
SphereScene::initCamera() const {
	set(0.0f, 0.0f, -500.0f, Globals::gCamera.worldPosition());
	set(0.0f, 0.0f, 1.0f, Globals::gCamera.worldLookVector());
}

void 
SphereScene::initPipeline() {
	assert(Globals::gD3D->mImmediateContext);
	ID3D11DeviceContext& context = *Globals::gD3D->mImmediateContext;

	//
	// Input Assembler Stage
	//

	// Set Primitive Topology
	context.IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_POINTLIST);


	//
	// Compute Shader Stage
	//

	// Shader
	context.CSSetShader(mShaders.mCS, nullptr, 0);

	// Set constant buffers
	{
		const uint32_t numBuffers = 1;
		ID3D11Buffer* buffers[numBuffers] = { nullptr };
		buffers[0] = mConstantBuffers.mCSImmutableBuffer.mBuffer;
		context.CSSetConstantBuffers(0, numBuffers, buffers);
	}


	//
	// Vertex Shader Stage
	//

	// Shader
	context.VSSetShader(mShaders.mVS, nullptr, 0);


	//
	// Geometry Shader Stage
	//

	// Shader
	context.GSSetShader(mShaders.mGS, nullptr, 0);

	// Set constant buffers
	{
		const uint32_t numBuffers = 1;
		ID3D11Buffer* buffers[numBuffers] = { nullptr };
		buffers[0] = mConstantBuffers.mGSPerFrameBuffer.mBuffer;
		context.GSSetConstantBuffers(0, numBuffers, buffers);
	}


	//
	// Pixel Shader Stage
	//

	// Shader
	context.PSSetShader(mShaders.mPS, nullptr, 0);

	// Resources
	{
		const uint32_t numResources = 1;
		ID3D11ShaderResourceView* resources[numResources] = { nullptr };
		resources[0] = mResources.mQuadTextureSRV;
		context.PSSetShaderResources(0, numResources, resources);
	}

	// Sampler state
	{
		ID3D11SamplerState* samplerStates[1] = { nullptr };
		samplerStates[0] = Globals::gPipeline->mLinearSS;
		context.PSSetSamplers(0, 1, samplerStates);
	}


	//
	// Output Merger Stage
	//

	// Blend factors to be used in blending
	{
		float blendFactor[] = { 0.0f, 0.0f, 0.0f, 0.0f };
		context.OMSetBlendState(Globals::gPipeline->mTransparentBS, blendFactor, 0xffffffff);
	}
}
